package com.schedule;

import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.domain.LiveEntity;
import com.domain.LocalFile;
import com.mapper.LiveMapper;
import com.mapper.LocalFileDao;
import com.service.LocalFileService;
import com.util.FileUtilLocal;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Date;

/***
 * 功能描述: 例外定时任务
 * @author v_kpkli
 * @date 2021/12/28
 */
@Component
public class PullLiveTask {

    private static final Logger LOGGER = LoggerFactory.getLogger(PullLiveTask.class);

    private static final String ROOT_PATH = System.getProperty("user.dir") + "\\file\\";

    private static final String CANXIANG_FILE_PATH = "D:\\xuexi\\zhibo\\canxiang\\";
    private static final String RTMP_URL = "rtmp://127.0.0.1:1935/live/";
    private static final String PULL_PATH = "http://localhost:8888/live?port=1935&app=live&stream=";

    @Autowired
    private LiveMapper liveMapper;

    @Autowired
    private LocalFileDao fileDao;

    @Autowired
    private LocalFileService localFileService;

    /***
     * 功能描述: 推送动漫直播
     * 每15分钟执行1次，并发执行
     */
    @Async
    @Scheduled(cron = "0 0/15 * ? * ?")
    public void pushCanXiang() throws Exception {
        LOGGER.info("开始推流：" + Thread.currentThread().getName());
        String titleConcat = "canxiang-1";
        String title = "canxiang";
        String titleCh = "东京残响";

        // 查询最近上传的东京残响集数
        QueryWrapper<LiveEntity> liveQueryWrapper = new QueryWrapper<>();
        liveQueryWrapper.lambda()
                .like(LiveEntity::getLiveUrl, title)
                .eq(LiveEntity::getIsDel, 0)
                .orderByDesc(LiveEntity::getCreateTime)
                .last("limit 1");
        LiveEntity selectOne = liveMapper.selectOne(liveQueryWrapper);

        // 计算文件名
        if (selectOne != null) {
            titleCh = selectOne.getLiveTitle();
            String[] split = titleCh.split("-");
            String liveTitle = split[0];
            Integer liveCount = Integer.parseInt(split[1]) + 1;
            if (liveCount == 13) {
                liveCount = 1;
            }
            titleConcat = title + "-" + liveCount;
            titleCh = liveTitle + "-" + liveCount;
        } else {
            titleCh = "东京残响-1";
        }
        // 生成文件地址
        String videoPath = CANXIANG_FILE_PATH + titleConcat + ".mp4";
        // 生成直播封面
        String coverPath = "videoCover" + File.separator + titleConcat + "_封面" + ".jpg";
        File coverFile = new File(ROOT_PATH + coverPath);
        String coverMd5 = FileUtilLocal.getFileMD5(coverFile);
        LocalFile localFile = localFileService.getLocalFileByMd5(coverMd5);
        // 如果已经生成了封面，就不再创建了
        if (localFile == null) {
            // 截取一帧作为封面
            FFmpegFrameGrabber ff = new FFmpegFrameGrabber(videoPath);
            ff.start();
            Frame frame = null;
            int i = 0;
            while (i < ff.getLengthInFrames()) {
                // 过滤前2-3秒，避免出现全黑的图片，依自己情况而定
                frame = ff.grabFrame();
                if ((i > 200) && (frame.image != null)) {
                    break;
                }
                i++;
            }
            Java2DFrameConverter converter = new Java2DFrameConverter();
            BufferedImage bufferedImage = converter.getBufferedImage(frame);
            ImageIO.write(bufferedImage, "jpg", coverFile);
            ff.close();
            ff.stop();

            // 存入封面
            LocalFile fileCover = new LocalFile();
            fileCover.setFileName(FileUtil.mainName(coverFile));
            fileCover.setFileType("videoCover");
            fileCover.setFileSuffix(FileUtil.getType(coverFile));
            fileCover.setFilePath(coverPath);
            fileCover.setFileMd5(coverMd5);
            fileDao.insert(fileCover);
        }

//        LocalFile localFile = VideoUtil.fetchCover(new File(videoPath), titleConcat + "_封面");
        LiveEntity liveEntity = new LiveEntity();
        try {
            // 新增直播
            liveEntity.setUserId(1L);
            liveEntity.setLiveUrl(PULL_PATH + titleConcat);
            liveEntity.setLiveCover(coverMd5);
            liveEntity.setLiveTitle(titleCh);
            liveEntity.setLiveType("1");
            liveEntity.setLiveOnline(1);
            liveEntity.setLivePartition("animation");
            liveEntity.setLiveIntroduction("后台上传的直播剧集");
            liveEntity.setCreateTime(new Date());
            liveEntity.setIsDel(0);
            liveMapper.insert(liveEntity);
            // 这种方法会卡
//            PushLiveUtil2.grabAndPush(videoPath, RTMP_URL + titleConcat);
            // 自己写的不卡
            grabAndPush(videoPath, RTMP_URL + titleConcat);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 删除直播
            liveEntity.setUpdateTime(new Date());
            liveEntity.setIsDel(1);
            liveMapper.updateById(liveEntity);
            // 删除封面
//            fileDao.deleteById(fileCover.getId());
            LOGGER.info("结束推流");
        }
    }

    public void grabAndPush(String inputUrl, String outputUrl) throws Exception {
        //设置FFmpeg日志级别
        avutil.av_log_set_level(avutil.AV_LOG_WARNING);
        FFmpegLogCallback.set();
        //以文件路径的方式传入视频，当然也支持以流的方式传入
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputUrl);
        //开始捕获视频流
        grabber.start();

        //用于将捕获到的视频流转换为输出URL的mp4格式。
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputUrl, grabber.getImageWidth(), grabber.getImageHeight());
        recorder.setFormat("flv");
        recorder.setVideoBitrate(grabber.getVideoBitrate()); // 设置视频比特率
        recorder.setFrameRate(grabber.getVideoFrameRate()); // 设置帧率
        recorder.setGopSize((int) grabber.getVideoFrameRate()); // 设置关键帧间隔
        // CRF 是一种用于控制视频/音频质量的参数，它允许在保持目标质量的同时动态地调整比特率。
        // 较低的CRF值表示更高的质量，但也可能导致较大的文件大小。 其默认值为23
//        recorder.setAudioOption("crf", "23");

        //设置音频编码为AAC
        if (grabber.getAudioChannels() > 0) {
            recorder.setAudioChannels(grabber.getAudioChannels());
            recorder.setAudioBitrate(grabber.getAudioBitrate());
            recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
        }
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
        //将解码后的帧记录到输出文件中
        //recorder.start通常用于处理已经解码成图像的视频数据
        recorder.start();

        Frame frame;
        long startTime = System.currentTimeMillis();
        int i = 0;
        int z = 0;
        System.out.println("推流开始时间:" + startTime);
        while ((frame = grabber.grab()) != null) {
            if ((i % 10000) == 0) {
                System.out.println("时间戳:" + recorder.getTimestamp() / 1000);
                System.out.println("推流用时:" + (System.currentTimeMillis() - startTime));
            }
            // 每1-2秒休眠一次
            if ((i % 100) == 0) {
                // 若时间戳大于传输用时 + 0.1s，休眠一半的时间
                if (recorder.getTimestamp() / 1000 > (System.currentTimeMillis() - startTime + 100)) {
                    Thread.sleep((recorder.getTimestamp() / 1000 - (System.currentTimeMillis() - startTime)) / 2);
                    z++;
                }
            }
            recorder.record(frame);
            i++;
        }
        System.out.println("总循环次数：" + i);
        System.out.println("总休眠次数：" + z);
        System.out.println("最终时间戳:" + recorder.getTimestamp() / 1000);
        System.out.println("最终推流用时:" + (System.currentTimeMillis() - startTime));
        recorder.close();
        grabber.close();
    }
}
